package com.ls.fw.open.support;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.method.HandlerMethod;

import com.ls.fw.commons.core.utils.collections.CollectionUtils;
import com.ls.fw.commons.open.AppSecretManager;
import com.ls.fw.commons.open.OpenException;
import com.ls.fw.commons.open.annotation.OpenApi;
import com.ls.fw.commons.open.utils.OpenUtil;
import com.ls.fw.data.helper.ValidatorHelper;
import com.ls.fw.data.result.ErrorResult;

/**
 * 校验器,签名校验
 * @author hc.tang
 *
 */
public abstract class OpenValidater implements Validater,InitializingBean {
	protected Logger logger = LoggerFactory.getLogger(getClass());
	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
	// 默认请求有效期30秒
	public static final int DEFAULT_EXPIRY_SECONDS = 30;

	public static final String APP_ID_NAME = "appId";
	public static final String SIGN_NAME = "sign";
	public static final String TIMESTAMP_NAME = "timestamp";
	
	protected String appIdName = APP_ID_NAME;
	protected String signName = SIGN_NAME;
	protected String timestampName = TIMESTAMP_NAME;
	
	protected int expirySeconds = DEFAULT_EXPIRY_SECONDS; // 有效期,单位秒
	protected Set<String> ignoreParamNames = new HashSet<String>();
	protected AppSecretManager appSecretManager;
	
	
	@Override
	public boolean validate(HttpServletRequest request,HttpServletResponse response,Map<String,String> map,HandlerMethod handler) {
		try{
			RequestContext requestContext = this.buildApiRequestContext(request, response, map,handler);
			validate(requestContext);
			
			return true;
		}catch (OpenException e) {
			fireException(request, response, e);
			return false;
		}
	}

	@Override
	public void fireException(HttpServletRequest request,
			HttpServletResponse response, OpenException e) {
		throw e;
	}
	
	
	@Override
	public boolean isMatch(HttpServletRequest request,
			HttpServletResponse response,HandlerMethod handler) {
		return true;
	}

	// 构建接口请求上下文
	protected RequestContext buildApiRequestContext(HttpServletRequest request,
			HttpServletResponse response,Map<String,String> map,HandlerMethod handler) {

		RequestContext requestContext =  new RequestContext();
		
		initRequestContext(requestContext, request, response, handler);
		
		Map<String,String> paramsMap = map;
		requestContext.setParamsMap(map);
		
		String appId = paramsMap.get(this.getAppIdName());
		String sign = paramsMap.get(this.getSignName());
		String timestampStr = paramsMap.get(this.getTimestampName());
		long timestamp = paramsMap.get(this.getTimestampName()) == null 
				? 0L : Long.parseLong(timestampStr);
		
		requestContext.setAppId(appId);
		requestContext.setSign(sign);
		requestContext.setTimestamp(timestamp);
		final Set<String> _ignoreParamNames = new HashSet<String>();
		if(CollectionUtils.isNotEmpty(this.ignoreParamNames)){
			_ignoreParamNames.addAll(this.ignoreParamNames);
		}if(CollectionUtils.isNotEmpty(requestContext.getIgnoreParamName())){
			_ignoreParamNames.addAll(requestContext.getIgnoreParamName());
		}
		requestContext.setIgnoreParamName(_ignoreParamNames);
		if(requestContext.getExpirySeconds() <= 0){
			requestContext.setExpirySeconds(expirySeconds);
		}
		return requestContext;
	}
	
	protected void initRequestContext(RequestContext requestContext,HttpServletRequest request,
			HttpServletResponse response,HandlerMethod handler) {
		requestContext.setNeedSign(true);
		requestContext.setExpirySeconds(expirySeconds);
	}

	// 验证参数
	private void validate(RequestContext requestContext) {
		if(requestContext.isNeedSign()){
			ErrorResult validateHolder = ValidatorHelper.getResult(ValidatorHelper
					.validate(requestContext));
			if (validateHolder!=null && validateHolder.isHasError()) { // 验证字段失败
				throw new OpenException("参数有误！").setData(validateHolder);
			}else{
				boolean hasPower = checkRole(requestContext);
				if(!hasPower){
					throw new OpenException("无权限！请联系管理员！");
				}
				if (!checkAppId(requestContext)) {
					throw new OpenException(""+signName+"无效");
				}
				// 验证签名
				if (!checkSign(requestContext)) {
					throw new OpenException("签名无效,"+signName+"=" + requestContext.getSign());
				}
			}
			if(requestContext.isNeedTimeout() && checkExpiry(requestContext)) {
				throw new OpenException("该请求已过期");
			}
		}
	}

	
	/**
	 * 
	 * @author lisheng
	 * @date 2016年6月11日 下午12:37:14
	 * @version V1.0
	 * @param requestContext
	 * @return
	 */
	private boolean checkRole(RequestContext requestContext) {
		boolean hasPower = true;
		String[] rolesStrings = this.getAppSecretManager().getRoles(requestContext.getAppId());
		if(rolesStrings != null){
			for (String str : rolesStrings) {
				if(!isExist(requestContext.getDenyRoles(), str, false)){
					if(!isExist(requestContext.getAllowRoles(), str, true)){
						hasPower = false;
					}
				}else{
					hasPower = false;
				}
			}
		}else{
			if(!isExist(requestContext.getDenyRoles(), null, false)){
				if(!isExist(requestContext.getAllowRoles(), null, true)){
					hasPower = false;
				}
			}else{
				hasPower = false;
			}
		}
		return hasPower;
	}
	
	private boolean isExist(String[] strs,String str,boolean defaultValue){
		boolean flag = defaultValue;
		if(strs!=null && strs.length>0){
			flag = !defaultValue;
			for (String string : strs) {
				if(string!=null && string.equals(str)){
					flag = true;
					break;
				}
			}
		}
		return flag;
	}
	
	// 检查请求有效期
	private boolean checkExpiry(RequestContext requestContext) {
		return requestContext.getServerStartTime() - requestContext.getTimestamp() > requestContext.getExpirySeconds() * 1000;
	}
	
	// 是否有效appId
	private boolean checkAppId(RequestContext requestContext) {
		String appId = requestContext.getAppId();
		return appSecretManager.isValidAppId(appId);
	}

	// 检查签名
	private boolean checkSign(RequestContext requestContext) {
		if (logger.isDebugEnabled()) {
			logger.debug("===开始验证签名");
		}

		String clientSign = requestContext.getSign();
		String secret = "";
		try {
			secret = appSecretManager.getSecret(requestContext.getAppId());
		} catch (Exception e1) {
			logger.error("---获取密码出错.secret=" + secret, e1);
			throw new OpenException("服务器异常！");
		}
		try {
			Map<String, String> paramsMap = requestContext.getAllParams();
			String serverSign = OpenUtil.buildSign(paramsMap, secret,
					requestContext.getIgnoreParamName());
			boolean isTrueSign = serverSign.equals(clientSign);
			logger.debug("签名验证结果:" + isTrueSign);

			return isTrueSign;
		} catch (IOException e) {
			logger.error("---构建签名出错.secret=" + secret, e);
			throw new OpenException("服务器异常！");
		}
	}

	protected static String getFullUrl(HttpServletRequest request) {
		String q = request.getQueryString();
		if(q==null){
			q = "";
		}
		return request.getRequestURL() + "?" + q;
	}

	public AppSecretManager getAppSecretManager() {
		return appSecretManager;
	}

	public void setAppSecretManager(AppSecretManager appSecretManager) {
		this.appSecretManager = appSecretManager;
	}

	public String getAppIdName() {
		return this.appIdName;
	}

	public void setAppIdName(String appIdName) {
		this.appIdName = appIdName;
	}

	public String getSignName() {
		return this.signName;
	}

	public void setSignName(String signName) {
		this.signName = signName;
	}

	public String getTimestampName() {
		return this.timestampName;
	}

	public void setTimestampName(String timestampName) {
		this.timestampName = timestampName;
	}

	public int getExpirySeconds() {
		return expirySeconds;
	}

	public void setExpirySeconds(int expirySeconds) {
		this.expirySeconds = expirySeconds;
	}

	public Set<String> getIgnoreParamNames() {
		return this.ignoreParamNames;
	}

	public void setIgnoreParamNames(Set<String> ignoreParamNames) {
		this.ignoreParamNames = ignoreParamNames;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		ignoreParamNames.add(signName);
	}
}
